﻿using System.Windows.Forms;

using System;
using System.Linq;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.IO;
using System.Threading;

namespace Win32.Input
{
    /// <summary>
    /// Keyboard
    /// </summary>
    public static partial class Keyboard
    {
       
        /// <summary>
        /// 键盘字符输入延迟，单位：毫秒
        /// </summary>
        public static uint KeyboardSendKeyDelay { get; set; } = 50;
        #region 锁定输入法

        /// <summary>
        /// 锁定输入法
        /// </summary>
        /// <param name="lockInput"></param>
        public static void LockInput(bool lockInput)
        {
            if (lockInput)
            {
                try
                {
                    RunWithTimeout(() =>
                    {
                        User32.SendMessage(new IntPtr(Win32.Const.HWND_BROADCAST), Win32.Const.WM_INPUTLANGCHANGEREQUEST, IntPtr.Zero, User32.LoadKeyboardLayout(Win32.Const.en_US, Win32.Const.KLF_ACTIVATE));

                    }, 1000);
                }
                catch (Exception)
                {
                    User32.PostMessage(Win32.Const.HWND_BROADCAST, Win32.Const.WM_INPUTLANGCHANGEREQUEST, IntPtr.Zero, User32.LoadKeyboardLayout(Win32.Const.en_US, Win32.Const.KLF_ACTIVATE));
                }
                 Wait.Delay(50);
            }
            else
            {
                try
                {
                    RunWithTimeout(() =>
                    {
                        User32.SendMessage(new IntPtr(Win32.Const.HWND_BROADCAST), Win32.Const.WM_INPUTLANGCHANGEREQUEST, IntPtr.Zero, User32.LoadKeyboardLayout(Win32.Const.cn_ZH, Win32.Const.KLF_ACTIVATE));

                    }, 1000);
                }
                catch (Exception)
                {
                    User32.PostMessage(Win32.Const.HWND_BROADCAST, Win32.Const.WM_INPUTLANGCHANGEREQUEST, IntPtr.Zero, User32.LoadKeyboardLayout(Win32.Const.cn_ZH, Win32.Const.KLF_ACTIVATE));
                }
            }
        }
        /// <summary>
        /// 超时执行方法
        /// </summary>
        /// <param name="action"></param>
        /// <param name="timeout">超时时间,单位毫秒</param>
        private static void RunWithTimeout(Action action, int timeout)
        {
            Thread threadToKill = null;
            Action wrappedAction = () =>
            {
                threadToKill = Thread.CurrentThread;
                action();
            };
            IAsyncResult result = wrappedAction.BeginInvoke(null, null);
            if (result.AsyncWaitHandle.WaitOne(timeout))
            {
                wrappedAction.EndInvoke(result);
            }
            else
            {
                threadToKill.Abort();
                throw new TimeoutException();
            }
        }
        #endregion

        #region Keyboard
        /// <summary>
        ///   KeyDown the  key.
        /// </summary>
        /// <param name="virtualKey">Keys</param>
        public static void KeyDown(Keys virtualKey)
        {
            User32.keybd_event((int)virtualKey, 0, 0, 0);
        }
        /// <summary>
        ///   KeyUp the  key.
        /// </summary>
        /// <param name="virtualKey">Keys</param>
        public static void KeyUp(Keys virtualKey)
        {
            User32.keybd_event((int)virtualKey, 0, 2, 0);
        }
        /// <summary>
        ///   KeyDown the  key and then KeyUp the  key.
        /// </summary>
        /// <param name="virtualKey">Keys</param>
        public static void KeyDownUp(Keys virtualKey)
        {
            KeyDown(virtualKey);
            KeyUp(virtualKey);
        }

        /// <summary>
		/// Chec k whether a specified key is currently held down
		/// </summary>
		/// <param name="key">The key</param>
		/// <returns>Return true if the key is held down, false otherwise</returns>
		public static bool IsKeyDown(Keys key)
        {
            return (User32.GetAsyncKeyState((int)key) & 0x8000) != 0;
        }
        /// <summary>
        /// 释放所有按键
        /// </summary>
        public static void ReleaseAllKeys()
        {
            byte[] states = new byte[256];
            for (int i = 0; i < 256; i++)
            {
                states[i] = 0;
            }
            User32.GetKeyboardState(states);
            for (int i = 0; i < 256; i++)
            {
                if ((states[i] & 0x80) != 0)
                {
                    User32.keybd_event((byte)i, 0, 2, 0);
                }
            }
        }
        /// <summary>
        /// Remove redundant modifiers which already contained in key
        /// </summary>
        /// <param name="key">The key</param>
        /// <param name="mods">Modifiers</param>
        /// <returns></returns>
        public static KeyModifiers RemoveRedundantMods(Keys key, KeyModifiers mods)
        {
            if (mods == KeyModifiers.None)
            {
                return mods;
            }

            if (key == Keys.Shift)
                mods &= ~KeyModifiers.Shift;

            if (key == Keys.Control)
                mods &= ~KeyModifiers.Ctrl;

            if (key == Keys.Alt)
                mods &= ~KeyModifiers.Alt;

            return mods;
        }

        /// <summary>
		/// Press down a key
		/// </summary>
		/// <param name="key">The key to be pressed down</param>
		/// <param name="mods">Modifiers</param>
		public static void KeyDown(Keys key, KeyModifiers mods = KeyModifiers.None)
        {
            mods = RemoveRedundantMods(key, mods);
            if ((mods & KeyModifiers.Alt) != 0)
            {
                KeyDown(Keys.Menu);
            }

            if ((mods & KeyModifiers.Ctrl) != 0)
            {
                KeyDown(Keys.ControlKey);
            }

            if ((mods & KeyModifiers.Shift) != 0)
            {
                KeyDown(Keys.ShiftKey);
            }
            KeyDown(key);
        }
        /// <summary>
        /// Release a key
        /// </summary>
        /// <param name="key">The key to be released</param>
        /// <param name="mods">Modifiers</param>
        public static void KeyUp(Keys key, KeyModifiers mods = KeyModifiers.None)
        {
            mods = RemoveRedundantMods(key, mods);

            KeyUp(key);

            if ((mods & KeyModifiers.Shift) != 0)
            {
                KeyUp(Keys.ShiftKey);
            }

            if ((mods & KeyModifiers.Ctrl) != 0)
            {
                KeyUp(Keys.ControlKey);
            }

            if ((mods & KeyModifiers.Alt) != 0)
            {
                KeyUp(Keys.Menu);
            }
        }

        /// <summary>
        /// Stroke a key
        /// </summary>
        /// <param name="key">The key to be stroked</param>
        /// <param name="mods">Modifiers</param>
        public static void KeyStroke(Keys key, KeyModifiers mods = KeyModifiers.None)
        {
            KeyDown(key, mods);
            KeyUp(key, mods);
        }


        /// <summary>
        /// 判断键盘大小写状态
        /// </summary>
        /// <returns></returns>
        public static bool isCapsLock()
        {
            short ks = User32.GetKeyState((int)Keys.CapsLock);
            return (ks == 1);
        }
        /// <summary>
        /// 判断数字键盘NumLock状态
        /// </summary>
        /// <returns></returns>
        public static bool isNumLock()
        {
            short ks = User32.GetKeyState((int)Keys.NumLock);
            return (ks == 1);
        }
        /// <summary>
        /// 设置键盘大小写状态
        /// </summary>
        /// <param name="upper">是否大写</param>
        /// <returns></returns>
        public static void SetCapsLock(bool upper)
        {
            short ks = User32.GetKeyState((int)Keys.CapsLock);
            if (upper)
            {
                if (ks != 1) KeyDownUp(Keys.CapsLock);
            }
            else
            {
                if (ks != 0) KeyDownUp(Keys.CapsLock);
            }
        }
        #endregion
        #region FLAUI
        /// <summary>
        /// Types the given text, one char after another.
        /// </summary>
        public static void Type(string text)
        {
            if (string.IsNullOrWhiteSpace(text)) return;
            try
            {
                Wait.Delay(50);
               LockInput(true);
                foreach (var c in text)
                {
                    Type(c);
                    Wait.Delay(KeyboardSendKeyDelay);
                }
                LockInput(false);

            }
            catch (Exception)
            {
               LockInput(false);
            }

        }
        /// <summary>
        /// Types the given character.
        /// </summary>
        public static void Type(char character)
        {

            var code = User32.VkKeyScan(character);
            // Check if the char is unicode or no virtual key could be found
            if (character > 0xFE || code == -1)
            {
                // It seems to be unicode
                SendInput(character, true, false, false, true);
                SendInput(character, false, false, false, true);
            }
            else
            {
                // Get the high-order and low-order byte from the code
                var high = (byte)(code >> 8);
                var low = (byte)(code & 0xff);

                // Check for caps lock and unset it
                var isCapsLockToggled = false;
                if ((User32.GetKeyState((int)VirtualKeyShort.CAPITAL) & 0x0001) != 0)
                {
                    isCapsLockToggled = true;
                    Type(VirtualKeyShort.CAPITAL);
                }

                // Check if there are any modifiers
                var modifiers = new List<VirtualKeyShort>();
                if (HasScanModifier(high, VkKeyScanModifiers.SHIFT))
                {
                    modifiers.Add(VirtualKeyShort.SHIFT);
                }
                if (HasScanModifier(high, VkKeyScanModifiers.CONTROL))
                {
                    modifiers.Add(VirtualKeyShort.CONTROL);
                }
                if (HasScanModifier(high, VkKeyScanModifiers.ALT))
                {
                    modifiers.Add(VirtualKeyShort.ALT);
                }
                // Press the modifiers
                foreach (var mod in modifiers)
                {
                    Press(mod);
                }
                // Type the effective key
                SendInput(low, true, false, false, false);
                SendInput(low, false, false, false, false);
                // Release the modifiers
                foreach (var mod in Enumerable.Reverse(modifiers))
                {
                    Release(mod);
                }

                // Re-toggle the caps lock if it was set before
                if (isCapsLockToggled)
                {
                    Type(VirtualKeyShort.CAPITAL);
                }
            }

        }

        /// <summary>
        /// Types the given keys, one by one.
        /// </summary>
        public static void Type(params VirtualKeyShort[] virtualKeys)
        {

            if (virtualKeys == null)
            {
                return;
            }
            foreach (var key in virtualKeys)
            {
                Press(key);
                Release(key);
            }

        }

        /// <summary>
        /// Types the given keys simultaneously (starting with the first).
        /// </summary>
        public static void TypeSimultaneously(params VirtualKeyShort[] virtualKeys)
        {
            if (virtualKeys == null)
            {
                return;
            }
            foreach (var key in virtualKeys)
            {
                Press(key);
            }
            foreach (var key in virtualKeys.Reverse())
            {
                Release(key);
            }
        }

        /// <summary>
        /// Types the given scan-code.
        /// </summary>
        public static void TypeScanCode(ushort scanCode, bool isExtendedKey)
        {
            PressScanCode(scanCode, isExtendedKey);
            ReleaseScanCode(scanCode, isExtendedKey);
        }

        /// <summary>
        /// Types the given virtual key-code.
        /// </summary>
        public static void TypeVirtualKeyCode(ushort virtualKeyCode)
        {
            PressVirtualKeyCode(virtualKeyCode);
            ReleaseVirtualKeyCode(virtualKeyCode);
        }

        /// <summary>
        /// Presses the given key.
        /// </summary>
        public static void Press(VirtualKeyShort virtualKey)
        {
            PressVirtualKeyCode((ushort)virtualKey);
        }

        /// <summary>
        /// Presses the given scan-code.
        /// </summary>
        public static void PressScanCode(ushort scanCode, bool isExtendedKey)
        {
            SendInput(scanCode, true, true, isExtendedKey, false);
        }

        /// <summary>
        /// Presses the given virtual key-code.
        /// </summary>
        public static void PressVirtualKeyCode(ushort virtualKeyCode)
        {
            SendInput(virtualKeyCode, true, false, false, false);
        }

        /// <summary>
        /// Releases the given key.
        /// </summary>
        public static void Release(VirtualKeyShort virtualKey)
        {
            ReleaseVirtualKeyCode((ushort)virtualKey);
        }

        /// <summary>
        /// Releases the given scan-code.
        /// </summary>
        public static void ReleaseScanCode(ushort scanCode, bool isExtendedKey)
        {
            SendInput(scanCode, false, true, isExtendedKey, false);
        }

        /// <summary>
        /// Releases the given virtual key-code.
        /// </summary>
        public static void ReleaseVirtualKeyCode(ushort virtualKeyCode)
        {
            SendInput(virtualKeyCode, false, false, false, false);
        }

        /// <summary>
        /// Presses the given keys and releases them when the returned object is disposed.
        /// </summary>
        public static IDisposable Pressing(params VirtualKeyShort[] virtualKeys)
        {
            foreach (var key in virtualKeys)
            {
                Press(key);
            }

            return new ActionDisposable(() =>
            {
                foreach (var key in virtualKeys.Reverse())
                {
                    Release(key);
                }
            });
        }

        /// <summary>
        /// Checks if a given byte has a specific VkKeyScan-modifier set.
        /// </summary>
        private static bool HasScanModifier(byte b, VkKeyScanModifiers modifierToTest)
        {
            return (VkKeyScanModifiers)(b & (byte)modifierToTest) == modifierToTest;
        }

        /// <summary>
        /// Effectively sends the keyboard input command.
        /// </summary>
        /// <param name="keyCode">The key code to send. Can be the scan code or the virtual key code.</param>
        /// <param name="isKeyDown">Flag if the key should be pressed or released.</param>
        /// <param name="isScanCode">Flag if the code is the scan code or the virtual key code.</param>
        /// <param name="isExtended">Flag if the key is an extended key.</param>
        /// <param name="isUnicode">Flag if the key is unicode.</param>
        private static void SendInput(ushort keyCode, bool isKeyDown, bool isScanCode, bool isExtended, bool isUnicode)
        {
            // Prepare the basic object
            var keyboardInput = new KEYBDINPUT
            {
                time = 0,
                dwExtraInfo = User32.GetMessageExtraInfo()
            };

            // Add the "key-up" flag if needed. By default it is "key-down"
            if (!isKeyDown)
            {
                keyboardInput.dwFlags |= KeyEventFlags.KEYEVENTF_KEYUP;
            }

            if (isScanCode)
            {
                keyboardInput.wScan = keyCode;
                keyboardInput.dwFlags |= KeyEventFlags.KEYEVENTF_SCANCODE;
                // Add the extended flag if the flag is set or the keycode is prefixed with the byte 0xE0
                // See https://msdn.microsoft.com/en-us/library/windows/desktop/ms646267(v=vs.85).aspx
                if (isExtended || (keyCode & 0xFF00) == 0xE0)
                {
                    keyboardInput.dwFlags |= KeyEventFlags.KEYEVENTF_EXTENDEDKEY;
                }
            }
            else if (isUnicode)
            {
                keyboardInput.dwFlags |= KeyEventFlags.KEYEVENTF_UNICODE;
                keyboardInput.wScan = keyCode;
            }
            else
            {
                keyboardInput.wVk = keyCode;
            }

            // Build the input object
            var input = INPUT.KeyboardInput(keyboardInput);
            // Send the command
            if (User32.SendInput(1, new[] { input }, INPUT.Size) == 0)
            {
                // An error occured
                var errorCode = Marshal.GetLastWin32Error();
                throw new Exception(string.Format("Could not send keyboard input. ErrorCode: {0}", errorCode));
            }
        }
        #endregion
    }

}
